﻿using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;

namespace Red.Launcher.ViewModels.Workspaces.Database.Sql
{
    public partial class Select
    {
        /// <summary>
        /// Comparison operators
        /// </summary>
        public enum Operator
        {
            [Description( "=" )]
            EqualTo,

            [Description( "IS" )]
            Is
        }

        /// <summary>
        /// Reference a column or non-literal value in a WHERE clause or similar
        /// </summary>
        public class ConditionVar : IAlias, ISql
        {
            /// <summary>
            /// The table/join/other where this value can be found
            /// </summary>
            public IAlias Source { get; set; }

            /// <summary>
            /// The name of the value
            /// </summary>
            public string Alias { get; set; }

            /// <summary>
            /// This is the SQL that is used in a WHERE clause or similar condition
            /// </summary>
            public string Sql => $"{Source.GetAliasPrefix()}{Alias}";
        }

        /// <summary>
        /// Reference a literal value in a WHERE clause or similar
        /// </summary>
        public class ConditionLiteral : ISql
        {
            /// <summary>
            /// A literal value you wish to compare against, such as an integer or string
            /// </summary>
            public object Value { get; set; }

            /// <summary>
            /// This is the SQL that is used in a WHERE clause or similar condition
            /// </summary>
            public string Sql => $"'{Value}'";
        }

        /// <summary>
        /// Reference a null value in a WHERE clause or similar
        /// </summary>
        public class ConditionNull : ISql
        {
            /// <summary>
            /// This is the SQL that is used in a WHERE clause or similar condition
            /// </summary>
            public string Sql => "NULL";
        }

        /// <summary>
        /// A comparison in sql
        /// </summary>
        public class Condition : ISql
        {
            #region Private Data

            private Operator _operator = Operator.EqualTo;

            #endregion Private Data

            #region Public Properties

            /// <summary>
            /// The left side of the comparison
            /// </summary>
            public ISql Left { get; set; }

            /// <summary>
            /// The right side of the comparison
            /// </summary>
            public ISql Right { get; set; }

            /// <summary>
            /// The operator used in the comparison
            /// </summary>
            public Operator Operator { get { return _operator; } set { _operator = value; } }

            /// <summary>
            /// This is the SQL that is used in a WHERE clause or similar condition
            /// </summary>
            public string Sql
            {
                get
                {
                    var field = typeof( Operator ).GetField( Operator.ToString() );
                    var attrs = field.GetCustomAttributes( typeof( DescriptionAttribute ), false );
                    var opDesc = attrs.FirstOrDefault() as DescriptionAttribute;

                    return $"{Left.Sql} {opDesc.Description} {Right.Sql}";
                }
            }

            #endregion Public Properties
        }

        /// <summary>
        /// A Class that represents a series of comparisons
        /// </summary>
        public class ConditionCollection : ISql
        {
            #region Private Data

            private List<Condition> _conditions = new List<Condition>();
            private string _keyword = "WHERE";

            #endregion Private Data

            #region Public Properties

            /// <summary>
            /// Every comparison in this collection
            /// </summary>
            public ICollection<Condition> Conditions => _conditions;

            /// <summary>
            /// The keyword that defines this series of conditions
            /// Defaults to "WHERE". For a join, this becomes "ON"
            /// </summary>
            public string Keyword { get { return ( Conditions.Count > 0 ) ? _keyword : ""; } set { _keyword = value; } }

            /// <summary>
            /// This is the SQL that is used in a WHERE clause or similar condition
            /// </summary>
            public string Sql { get { return string.Format( "{0} {1}", Keyword, string.Join( " AND ", from condition in Conditions select condition.Sql ) ); } }

            #endregion Public Properties

            #region Public Methods

            /// <summary>
            /// Utility function to simplify adding new conditions to the collection and to reduce boilerplate
            /// </summary>
            public void Add( Condition condition )
            {
                Conditions.Add( condition );
            }

            /// <summary>
            /// Utility function to simplify adding new conditions to the collection and to reduce boilerplate
            /// </summary>
            public void AddVar( string leftName, string rightName, IAlias leftSource = null, IAlias rightSource = null )
            {
                Conditions.Add
                (
                    new Condition()
                    {
                        Left = new ConditionVar()
                        {
                            Alias = leftName,
                            Source = leftSource
                        },
                        Right = new ConditionVar()
                        {
                            Alias = rightName,
                            Source = rightSource,
                        }
                    }
                );
            }

            /// <summary>
            /// Utility function to simplify adding new conditions to the collection and to reduce boilerplate
            /// </summary>
            public void AddLiteral( string leftName, object value, IAlias leftSource = null )
            {
                Conditions.Add
                (
                    new Condition()
                    {
                        Left = new ConditionVar()
                        {
                            Alias = leftName,
                            Source = leftSource
                        },
                        Right = new ConditionLiteral()
                        {
                            Value = value
                        }
                    }
                );
            }

            /// <summary>
            /// Utility function to simplify adding new conditions to the collection and to reduce boilerplate
            /// </summary>
            public void AddNull( string leftName, IAlias leftSource = null )
            {
                Conditions.Add
                (
                    new Condition()
                    {
                        Left = new ConditionVar()
                        {
                            Alias = leftName,
                            Source = leftSource
                        },
                        Right = new ConditionNull(),
                        Operator = Operator.Is
                    }
                );
            }

            /// <summary>
            /// Utility function to simplify adding new conditions to the collection and to reduce boilerplate
            /// </summary>
            public void AddSubQuery( string leftName, ISql rightQuery, IAlias leftSource = null )
            {
                Conditions.Add
                (
                    new Condition()
                    {
                        Left = new ConditionVar()
                        {
                            Alias = leftName,
                            Source = leftSource
                        },
                        Right = rightQuery
                    }
                );
            }

            #endregion Public Methods
        }
    }
}
